package com.unknow.first.mfa.config;

import static org.cloud.constant.LoginTypeConstant.LoginTypeEnum.LOGIN_BY_ADMIN_USER;
import static org.cloud.constant.MfaConstant.CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE;

import com.unknow.first.util.GoogleAuthenticatorUtil;
import java.io.IOException;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.Setter;
import org.cloud.constant.CoreConstant;
import org.cloud.context.RequestContext;
import org.cloud.context.RequestContextManager;
import org.cloud.core.redis.RedisUtil;
import org.cloud.entity.LoginUserDetails;
import org.cloud.exception.BusinessException;
import org.cloud.utils.HttpServletUtil;
import org.cloud.utils.IPUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * mfa验证器，目前只支持google
 */
@ConfigurationProperties(prefix = "system.mfa")
@Configuration
@ConditionalOnProperty(prefix = "system.mfa", name = "enabled", matchIfMissing = true)
public class MfaFilterConfig {

    public final static String __MFA_TOKEN_USER_CACHE_KEY = "system:mfa:user:verify:result:";  // 校验结果
    public final static String __MFA_TOKEN_USER_GOOGLE_SECRET_CACHE_KEY = "system:mfa:user:secret:result:"; // 谷歌key

    @Setter
    private List<String> excludeUri;      // 默认为内部调用的url也可以自己添加
    private final CoreConstant.MfaAuthType mfaAuthType = CoreConstant.MfaAuthType.GOOGLE;  //默认为google验证

    @Bean
    public FilterRegistrationBean<?> mfaWebFilter() {
        this.excludeUri.add("/v2/api-docs");
        this.excludeUri.add("/inner/**/*");
        this.excludeUri.add("/user/verify/generate/*");
        this.excludeUri.add("/user/mfa/**");
        this.excludeUri.add("/app/userRef/isBindLoginIp");
        this.excludeUri.add("/app/userRef/ipLoginLockFlagOpen");
        this.excludeUri.add("/app/userRef/changeLoginIp");
        this.excludeUri.add("/app/userRef/getCurrentIp");
        this.excludeUri.add("/user/menu/getMenus");
        FilterRegistrationBean<?> registration = new FilterRegistrationBean<>(new MfaWebFilter(excludeUri));
        registration.addUrlPatterns("/*");
        registration.setName("mfaWebFilter");
        registration.setOrder(100);
        return registration;
    }

    static class MfaWebFilter extends OncePerRequestFilter {

        private final List<String> noMfaCheckUrl;      // 默认为内部调用的url也可以自己添加

        public MfaWebFilter(List<String> noMfaCheckUrl) {
            this.noMfaCheckUrl = noMfaCheckUrl;
        }

        @Override
        protected void doFilterInternal(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse,
            @NotNull FilterChain filterChain) throws ServletException, IOException {
            if (HttpServletUtil.single().isExcludeUri(httpServletRequest, noMfaCheckUrl)) {
                filterChain.doFilter(httpServletRequest, httpServletResponse);
                return;
            }
            // 如果数据字典中关闭了双因子校验，那么不进行双因子校验，关闭数据字典的全局配置，防止密码泄露后可以更改此项目绕过google
//            TSystemDicItem dicItem = SystemDicUtil.single().getDicItem("systemConfig", "isMfaVerify");
//            if (dicItem != null && StringUtils.hasLength(dicItem.getDicItemValue()) && "false".equals(dicItem.getDicItemValue())) {
//                filterChain.doFilter(httpServletRequest, httpServletResponse);
//                return;
//            }
            RequestContext currentRequestContext = RequestContextManager.single().getRequestContext();
            LoginUserDetails user = currentRequestContext.getUser();
            // 只有需要登录鉴权的接口并且用户类型为管理员才需要校验双因子
            if (user == null || (!LOGIN_BY_ADMIN_USER.userType.equals(user.getUserType()))) {
                filterChain.doFilter(httpServletRequest, httpServletResponse);
                return;
            }
            try {
                GoogleAuthenticatorUtil.single().verifyCurrentUserBindGoogleKey();
            } catch (BusinessException businessException) {
                try {
                    HttpServletUtil.single().handlerBusinessException(businessException, httpServletResponse);
                } catch (Exception e) {
                    logger.warn("校验google验证绑定情况失败时将信息写入到response时失败，" + e.getMessage());
                }
                logger.error("用户未绑定google");
                return;
            }
            final String ipHash = RedisUtil.single().getMd5Key(IPUtil.single().getIpAddress(httpServletRequest));
            Boolean isValidatePass = RedisUtil.single().get(__MFA_TOKEN_USER_CACHE_KEY + user.getId() + ":" + ipHash);
            // 如果规定时间内校验过并且未过期，那么不校验
            if (isValidatePass != null && isValidatePass) {
                filterChain.doFilter(httpServletRequest, httpServletResponse);
            } else {
                try {
                    HttpServletUtil.single()
                        .handlerBusinessException(new BusinessException(CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE.value(),
                            CORRELATION_GOOGLE_NOT_VERIFY_OR_EXPIRE.description(), HttpStatus.BAD_REQUEST.value()), httpServletResponse);
                } catch (Exception e) {
                    logger.warn("校验google验证是否有效时将信息写入到response时失败，" + e.getMessage());
                }
                logger.error("用户google未校验或者校验失效了");
            }
        }
    }
}
